package ge.telasi.tasks.controller;

import ge.telasi.tasks.TaskException;
import ge.telasi.tasks.model.TaskAttachment;
import ge.telasi.tasks.model.FlowPermission;
import ge.telasi.tasks.model.Task;
import ge.telasi.tasks.model.TaskBody;
import ge.telasi.tasks.model.TaskList;
import ge.telasi.tasks.model.TaskReceiver;
import ge.telasi.tasks.model.TaskReceiverIndex;
import ge.telasi.tasks.model.TaskRelation;
import ge.telasi.tasks.model.User;
import ge.telasi.tasks.model.validation.FileValidator;
import ge.telasi.tasks.model.validation.TaskValidator;
import ge.telasi.tasks.report.Report;
import ge.telasi.tasks.report.TaskReport;
import ge.telasi.tasks.search.Condition;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import oracle.toplink.essentials.config.HintValues;
import oracle.toplink.essentials.config.TopLinkQueryHints;
import static ge.telasi.tasks.model.Task.*;

/**
 * @author dimitri
 */
public class TaskController extends Controller {
    
    static int[] ALL_TASK_STATUSES = {STATUS_DRAFT, STATUS_OPEN, STATUS_PAUSED, STATUS_COMPLETE_OK,
        STATUS_COMPLETE_CANCEL, STATUS_CLOSED_OK, STATUS_CLOSED_CANCEL};

    static final String GENERATED_HEADER = "<<ავტომატურად გენერირებული ტექსტი>>\n\n";

    // <editor-fold defaultstate="collapsed" desc="Flow Permission Management">

    public FlowPermission findFlowPermission(EntityManager em, int permissionId) {
        return em.find(FlowPermission.class, permissionId);
    }

    public List<FlowPermission> getFlowPermissions(EntityManager em, int taskType) {
        return em.createNamedQuery("FlowPermission.getByType")
                .setParameter("taskType", taskType)
                .getResultList();
    }

    private FlowPermission findSimilar(EntityManager em, FlowPermission permission) {
        int type = permission.getTaskType();
        List<FlowPermission> permissions = getFlowPermissions(em, type);
        for (FlowPermission perm : permissions) {
            boolean sameRole = permission.getRole().equals(perm.getRole());
            boolean sameOper = permission.getOperation() == perm.getOperation();
            boolean sameStat = permission.getTaskStatus() == perm.getTaskStatus();
            if (sameRole && sameOper && sameStat) {
                if (permission.getOperation() == FlowPermission.OPERATION_CHANGE_STATUS) {
                    if (permission.getFinalStatus().equals(perm.getFinalStatus())) {
                        return perm;
                    }
                } else {
                    return perm;
                }
            }

        }
        return null;
    }

    public FlowPermission createPermission(EntityManager em, User user, FlowPermission permission) {
        // admin privilegies required
        requireAdmin(em, user);
        // check whether role and operation are compatible
        List<String> possibleRoles = FlowPermission.OPERATION_ROLES.get(permission.getOperation());
        if (!possibleRoles.contains(permission.getRole())) {
            throw new TaskException("role is not compatible with this operation");
        }
        // check for similar permission
        if (findSimilar(em, permission) != null) {
            throw new TaskException("existing flow persmission");
        }
        if (permission.getOperation() == FlowPermission.OPERATION_CHANGE_STATUS) {
            if (permission.getFinalStatus() == null) {
                throw new TaskException("define statuses");
            }
        } else {
            permission.setFinalStatus(null);
        }
        em.persist(permission);
        return permission;
    }

    private void deletePermissionInternal(EntityManager em, FlowPermission permission) {
        FlowPermission exst = findFlowPermission(em, permission.getId());
        em.remove(exst);
    }

    public void deletePermission(EntityManager em, User user, FlowPermission permission) {
        requireAdmin(em, user);
        deletePermissionInternal(em, permission);
    }

    public void deletePermissions(EntityManager em, User user, List<FlowPermission> permissions) {
        requireAdmin(em, user);
        for (FlowPermission permission : permissions) {
            deletePermissionInternal(em, permission);
        }
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Checking Permission">

    private boolean userIsInList(User user, List<TaskReceiver> receivers) {
        for (TaskReceiver receiver : receivers) {
            if (receiver.getUser() != null && receiver.getUser().equals(user)) {
                return true;
            } else if (receiver.getGroup() != null) {
                for (User groupUser : receiver.getGroup().getUsers()) {
                    if (groupUser.equals(user)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    public String[] getUserRoles(Task task, User user, TaskAttachment attachment) {
        List<String> roles = new ArrayList<String>();
        if (task.getOwner().equals(user)) {
            roles.add(Task.ROLE_OWNER);
        }
        if (task.getPreparedBy().equals(user)) {
            roles.add(Task.ROLE_PREPARED_BY);
        }
        if (userIsInList(user, task.getAssignees())) {
            roles.add(Task.ROLE_ASSIGNEE);
        }
        if (userIsInList(user, task.getCopies())) {
            roles.add(Task.ROLE_CARBON_COPY);
        }
        if (attachment != null && attachment.getOwner().equals(user)) {
            roles.add(Task.ROLE_FILE_OWNER);
        }
        return roles.toArray(new String[] {});
    }

    public void validatePermission(EntityManager em, Task task, User user, int operation, Integer toStatus, TaskAttachment attachment) {
        em.refresh(task);
        if (!checkPermission(em, task, user, operation, toStatus, attachment)) {
            String msgKey = null;
            switch(operation) {
                case FlowPermission.OPERATION_CHANGE_PRIORITY:
                    msgKey = "TaskController.permissionValidation.changePriority";
                    break;
                case FlowPermission.OPERATION_CHANGE_RECEIVERS:
                    msgKey = "TaskController.permissionValidation.changeReceivers";
                    break;
                case FlowPermission.OPERATION_CHANGE_STATUS:
                    msgKey = "TaskController.permissionValidation.changeStatus";
                    break;
                case FlowPermission.OPERATION_CHANGE_SUBJECT:
                    msgKey = "TaskController.permissionValidation.changeSubject";
                    break;
                case FlowPermission.OPERATION_ADD_ATTACHMENT:
                    msgKey = "TaskController.permissionValidation.addAttachment";
                    break;
                case FlowPermission.OPERATION_REMOVE_ATTACHMENT:
                    msgKey = "TaskController.permissionValidation.removeAttachment";
                    break;
                case FlowPermission.OPERATION_DOWNLOAD_ATTACHMENT:
                    msgKey = "TaskController.permissionValidation.downloadAttachment";
                    break;
                case FlowPermission.OPERATION_DELETE_TASK:
                    msgKey = "TaskController.permissionValidation.deleteTask";
                    break;
                case FlowPermission.OPERATION_ADD_PARENT:
                    msgKey = "TaskController.permissionValidation.addParentRelation";
                    break;
                case FlowPermission.OPERATION_REMOVE_PARENT:
                    msgKey = "TaskController.permissionValidation.deleteParentRelation";
                    break;
            }
            String msg = getLocaleManager().getMessage(msgKey);
            throw new TaskException(msg);
        }
    }

    public boolean checkPermission(EntityManager em, Task task, User user, int operation, Integer toStatus, TaskAttachment attachment) {
        if (operation == FlowPermission.OPERATION_DOWNLOAD_ATTACHMENT) // everyone can download
            return true;
        int type = task.getType();
        String[] roles = getUserRoles(task, user, attachment);
        List<FlowPermission> permissions = getFlowPermissions(em, type);
        for (FlowPermission permission : permissions) {
            if (permission.getOperation() == operation) {
                for (String role : roles) {
                    if (role.equals(permission.getRole())) {
                        if (permission.getTaskStatus() == task.getTaskStatus()) {
                            if (operation == FlowPermission.OPERATION_CHANGE_STATUS) {
                                int finalStatus = toStatus;
                                if (permission.getFinalStatus().equals(finalStatus)) {
                                    return true;
                                }
                            } else {
                                return true;
                            }
                        }
                        break;
                    }
                }
            }
        }
        return false;
    }

    public Integer[] getAllowedStatuses(EntityManager em, Task task, User user) {
        task = findTask(em, task.getId());
        em.refresh(task);
        List<Integer> allowed = new ArrayList<Integer>();
        for (int status : ALL_TASK_STATUSES) {
            if (checkPermission(em, task, user, FlowPermission.OPERATION_CHANGE_STATUS, status, null)) {
                allowed.add(status);
            }
        }
        return allowed.toArray(new Integer[] {});
    }

    public void validateRelationNotCyclic(EntityManager em, Task task, Task relationCandidate, boolean parent) {
        long parentId = relationCandidate.getId();
        if (parentId == task.getId()) { // self-reference
            throwRelationCycleError(relationCandidate);
        }
        List<TaskRelation> relations = parent ? task.getParents() : task.getChildren();
        for (TaskRelation relation : relations) {
            Task relationTask = parent ? relation.getParent() : relation.getChild();
            if (relationTask.getId().equals(parentId)) {
                throwRelationCycleError(relationCandidate);
            }
            validateRelationNotCyclic(em, relationTask, relationCandidate, parent);
        }
    }
    private void throwRelationCycleError(Task relationCandidate) {
        StringBuilder b = new StringBuilder("<html><body>ციკლური ან საკუთარ თავზე მიმართვა. ");
        b.append("<br><br>");
        b.append(getTaskReferenceText(relationCandidate));
        b.append("</body></html>");
        throw new TaskException(b.toString());
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Task Operations">

    public Task findTask(EntityManager em, long taskId) {
        try {
            return (Task) em.createQuery("SELECT t FROM Task t WHERE t.id = :id")
                    .setHint(TopLinkQueryHints.REFRESH, HintValues.TRUE)
                    .setParameter("id", taskId)
                    .getSingleResult();
        } catch (NoResultException ex) {
            throw new TaskException("დავალება ვერ მოიძებნა. განაახლეთ ხედი და სცადეთ ხელახლა.");
        }
    }

    public TaskAttachment findAttachment(EntityManager em, long id) {
        return em.find(TaskAttachment.class, id);
    }

    public void markAsRead(EntityManager em, long taskId, long userId) {
        String sql = "SELECT index FROM TaskReceiverIndex index " +
                "WHERE index.task.id = :task AND index.user.id = :user";
        try {
            TaskReceiverIndex index = (TaskReceiverIndex) em
                    .createQuery(sql)
                    .setParameter("task", taskId)
                    .setParameter("user", userId)
                    .getSingleResult();
            index.setNew(false);
        } catch (NoResultException nrex) {}
    }

    public void indexTask(EntityManager em, long taskId) {
        Query q = em.createNativeQuery("BEGIN task_indexing_make(?); END;");
        q.setParameter(1, taskId);
        q.executeUpdate();
    }

    private void deleteTaskWithoutCheckingPermission(EntityManager em, Task task, boolean removeAttachemnts) {
        // get task from database
        Task exst = findTask(em, task.getId());

        // remove all receivers
        for (TaskReceiver receiver : exst.getReceivers()) {
            em.remove(receiver);
        }

        // remove all bodies
        for (TaskBody body : exst.getBodies()) {
            em.remove(body);
        }

        // remove attachments, if allowed
        if (removeAttachemnts) {
            for (TaskAttachment data : exst.getAttachments()) {
                em.remove(data);
            }
        }

        // remove children relations
        for (TaskRelation child : exst.getChildren()) {
            em.remove(child);
            child.getChild().removeParent(child);
        }

        // remove parent relations
        for (TaskRelation parent : exst.getParents()) {
            em.remove(parent);
            parent.getParent().removeChild(parent);
        }

        // delete task itself
        em.remove(exst);
    }

    public void deleteTask(EntityManager em, User user, Task task) {
        task = findTask(em, task.getId());
        validatePermission(em, task, user, FlowPermission.OPERATION_DELETE_TASK, null, null);
        deleteTaskWithoutCheckingPermission(em, task, true);
        new NotificationManager().notifyTaskDeletion(em, task, user);
    }

    public Task createTask(EntityManager em, User user, Task task, User forUser, boolean draft) {
        boolean isExistingTask = task.getId() != null;
        User preparedBy = null;
        if (isExistingTask) { // old task
            int finalStatus = draft ? Task.STATUS_DRAFT : Task.STATUS_OPEN;
            Task exst = findTask(em, task.getId());
            preparedBy = exst.getPreparedBy();
            validatePermission(em, exst, user, FlowPermission.OPERATION_CHANGE_STATUS, finalStatus, null);
            deleteTaskWithoutCheckingPermission(em, task, false);
            task.setId(null);
        }

        // get users
        UserController uc = new UserController();
        user = uc.find(em, user.getUsername());
        forUser = forUser == null ? user : uc.find(em, forUser.getUsername());

        // Create body. Body is created only for non-drafts.
        TaskBody body = null;
        if (!draft) {
            body = new TaskBody();
            body.setBody(task.getLastBody());
            body.setActive(true);
            body.setOwner(user);
            body.setTaskStatus(Task.STATUS_OPEN);
        }

        // enable task
        task.setOwner(forUser);
        task.setPreparedBy(preparedBy == null ? user : preparedBy);
        task.setActive(true);
        task.setType(Task.TYPE_GENERAL);
        task.setTaskStatus(draft ? Task.STATUS_DRAFT : Task.STATUS_OPEN);
        if (draft) {
            task.setNumber("draft"); // draft is marked with this number
        } else {
            NumberFormat nf = new DecimalFormat("000");
            task.setNumber(user.getCode() + "/" + nf.format(user.getLastTaskSequence()));
            user.setLastTaskSequence(user.getLastTaskSequence() + 1);
        }
        task.setStructure(new StructureController().findStructure(em, task.getStructure().getId()));

        // Save attachments for later processing.
        List<TaskAttachment> attachments = task.getAttachments();
        task.setAttachments(new ArrayList<TaskAttachment>());
        task.setHasAttachment(false);

        // create receivers
        List<TaskReceiver> receivers = task.getReceivers();
        task.setReceivers(new ArrayList<TaskReceiver>());
        for (TaskReceiver receiver : receivers) {
            if (receiver.getUser() != null) {
                receiver.setGroup(null);
                receiver.setUser(uc.find(em, receiver.getUser().getUsername()));
            } else if (receiver.getGroup() != null) {
                receiver.setUser(null);
                receiver.setGroup(uc.findGroup(em, receiver.getGroup().getId()));
            }
            receiver.setActive(true);
            receiver.setId(null);
        }

        // create relations (only parent at creation)
        List<TaskRelation> relations = task.getParents();
        task.setParents(new ArrayList<TaskRelation>());
        task.setHasChild(false);
        task.setHasParent(false);
        for (TaskRelation relation : relations) {
            Task parentTask = findTask(em, relation.getParent().getId());
            relation.setParent(parentTask);
        }

        // persist task
        em.persist(task);
        
        // persist relations (parents for the new task)
        if (relations != null && !relations.isEmpty()) {
            for (TaskRelation relation : relations) {
                relation.setChild(task);
                em.persist(relation);
                task.addParent(relation);
                relation.getParent().addChild(relation);
            }
        }

        // add parent's body
        addParentBodyForNewTask(em, user, task);
        em.flush();

        // persist body (only non-drafts)
        if (!draft) {
            body.setTask(task);
            em.persist(body);
            task.getBodies().add(body);
        }

        // persist receivers
        for (TaskReceiver receiver : receivers) {
            receiver.setTask(task);
            em.persist(receiver);
            task.getReceivers().add(receiver);
        }

        // check attachments
        if (attachments != null) {
            for (TaskAttachment att : attachments) {
                TaskAttachment attachment = findAttachment(em, att.getId());
                attachment.setTask(task);
                task.addAttachment(attachment);
            }
        }

        // call task indexing
        indexTask(em, task.getId());
        markAsRead(em, task.getId(), user.getId());

        // send notification
        new NotificationManager().notifyCreation(em, user, task);

        // this is a result
        return task;
    }

    private String getTaskReferenceText(Task task) {
        StringBuilder b = new StringBuilder();
        b.append("\n   ** ");
        b.append(task.getNumber());
        b.append(": ");
        b.append(task.getSubject());
        b.append(" (");
        b.append(task.getOwner().getFullName());
        b.append(")");
        return b.toString();
    }

    public void addParentBodyForNewTask(EntityManager em, User user, Task task) {
        // don't process drafts
        if (task.getTaskStatus() == STATUS_DRAFT) {
            return;
        }

        // create relation text
        String relationBodyText = null;
        if (!task.getParents().isEmpty()) {
            StringBuilder b = new StringBuilder(GENERATED_HEADER);
            b.append("ამ დავალებას საფუძვლად უდევს შემდეგი დავალებები: ");
            for (TaskRelation relation : task.getParents()) {
                Task parentTask = relation.getParent();
                b.append(getTaskReferenceText(parentTask));
            }
            relationBodyText = b.toString();
        }

        // add body which describes relation
        if (relationBodyText != null) {
            TaskBody relationBody = new TaskBody();
            relationBody.setBody(relationBodyText);
            relationBody.setOwner(user);
            relationBody.setTask(task);
            relationBody.setTaskStatus(task.getTaskStatus());
            relationBody.setActive(true);
            relationBody.setData("parent_add");
            em.persist(relationBody);
            task.getBodies().add(relationBody);
        }
    }

    public Task addParentRelation(EntityManager em, User user, Task task, List<TaskRelation> relations) {
        // getting existing task
        Task exst = findTask(em, task.getId());
        
        // check permission on this action
        validatePermission(em, exst, user, FlowPermission.OPERATION_ADD_PARENT, null, null);

        // validate cycle links
        for (TaskRelation relation : relations) {
            Task parent = relation.getParent();
            validateRelationNotCyclic(em, exst, parent, true);
            if (parent.getTaskStatus() == STATUS_DRAFT) { // draft is not allowed as a parent
                throw new TaskException("დავალება უნდა იყოს გაგზავნილი: " + parent.getNumber());
            }
        }

        // save new relations
        StringBuilder parentAppenedText = new StringBuilder(GENERATED_HEADER);
        parentAppenedText.append("ამ დავალებაში დაემატა შემდეგი საფუძვლები:");
        for (TaskRelation relation : relations) {
            Task parent = findTask(em, relation.getParent().getId());
            relation.setParent(parent);
            relation.setChild(exst);
            em.persist(relation);
            exst.addParent(relation);
            parent.addChild(relation);
            parentAppenedText.append(getTaskReferenceText(parent));
        }

        // add body
        TaskBody body = new TaskBody();
        body.setTaskStatus(exst.getTaskStatus());
        body.setOwner(user);
        body.setTask(exst);
        body.setBody(parentAppenedText.toString());
        body.setActive(true);
        body.setData("parent_add");
        em.persist(body);
        exst.getBodies().add(body);

        // update validation
        taskUpdated(em, user, exst, FlowPermission.OPERATION_ADD_PARENT);

        // notify parties
        new NotificationManager().notifyParentManipulation(em, user, exst, relations, false);

        // return result
        return exst;
    }

    public Task removeParentRelation(EntityManager em, User user, Task task, List<TaskRelation> relations) {
        // getting existing task
        Task exst = findTask(em, task.getId());

        // check permission on this action
        validatePermission(em, exst, user, FlowPermission.OPERATION_REMOVE_PARENT, null, null);

        // save new relations
        StringBuilder parentAppenedText = new StringBuilder(GENERATED_HEADER);
        parentAppenedText.append("ამ დავალებაში მოაკლდა შემდეგი საფუძვლები:");
        for (TaskRelation rel : relations) {
            TaskRelation relation = null;
            for (TaskRelation temp : exst.getParents()) {
                if (temp.getParent().getId().equals(rel.getParent().getId())) {
                    relation = temp;
                    break;
                }
            }
            parentAppenedText.append(getTaskReferenceText(relation.getParent()));
            relation.getParent().removeChild(relation);
            relation.getChild().removeParent(relation);
            em.remove(relation);
        }

        // add body
        TaskBody body = new TaskBody();
        body.setTaskStatus(exst.getTaskStatus());
        body.setOwner(user);
        body.setTask(exst);
        body.setBody(parentAppenedText.toString());
        body.setActive(true);
        body.setData("parent_delete");
        em.persist(body);
        exst.getBodies().add(body);

        // update validation
        taskUpdated(em, user, exst, FlowPermission.OPERATION_REMOVE_PARENT);

        // notify parties
        new NotificationManager().notifyParentManipulation(em, user, exst, relations, true);

        return exst;
    }

    public TaskList findTasks(EntityManager em, User user, int page, int perPage, List<Condition> conditions) {
        StringBuilder where = new StringBuilder();
        where.append(" WHERE (indx.user.id = :user_id ) ");
        if (conditions != null && !conditions.isEmpty()) {
            where.append(" AND ( ");
            where.append(Condition.getFullCondition(conditions));
            where.append(")");
        }

        String whereText = where.toString();
        boolean containsReceiverQuery = whereText.contains("RECEIVER");

        StringBuilder sql1 = new StringBuilder();
        StringBuilder sql2 = new StringBuilder();
        sql1.append("SELECT indx FROM TaskReceiverIndex indx ");
        sql2.append("SELECT COUNT(indx.id) FROM TaskReceiverIndex indx ");

        if (containsReceiverQuery) {
            sql1.append(", IN (indx.task.receivers) AS RECEIVER ");
            sql2.append(", IN (indx.task.receivers) AS RECEIVER ");
        }

        sql1.append(whereText);
        sql1.append(" ORDER BY indx.task.id DESC");

        sql2.append(whereText);
        sql2.append(" AND indx.task.status NOT IN (:dummy) "); // DUMMY condition, because bug with TopLink

        Query q1 = em.createQuery(sql1.toString())
                .setHint(TopLinkQueryHints.REFRESH, HintValues.TRUE) // refresh is essential here
                .setParameter("user_id", user.getId())
                .setFirstResult(page * perPage)
                .setMaxResults(perPage);
        if (conditions != null && !conditions.isEmpty()) {
            Condition.applyParameters(q1, conditions);
        }
        List<TaskReceiverIndex> tasks = q1.getResultList();

        // receiver list should be known
        for (TaskReceiverIndex index : tasks) {
            index.getTask().getAssignees().size();
        }
        
        Long count = 0L;
        if (tasks.size() > 0) {
            try {
                Query q2 = em.createQuery(sql2.toString())
                        .setParameter("user_id", user.getId())
                        .setParameter("dummy", -2);
                if (conditions != null && !conditions.isEmpty()) {
                    Condition.applyParameters(q2, conditions);
                }
                count = (Long) q2.getResultList().get(0);
            } catch (NoResultException nre) {}
        }
        TaskList taskList = new TaskList();
        taskList.addAll(tasks);
        taskList.setTotalCount(count.intValue());
        return taskList;
    }

    public Task changeReceivers(EntityManager em, User user, Task task, List<TaskReceiver> receivers, boolean copy) {
        if (!copy && receivers.isEmpty()) {
            String msg = getLocaleManager().getMessage("TaskController.emptyAssigneeList");
            throw new TaskException(msg);
        }
        Task exst = findTask(em, task.getId());
        List<TaskReceiver> oldReceivers = new ArrayList<TaskReceiver>();
        oldReceivers.addAll(exst.getReceivers());
        validatePermission(em, exst, user, FlowPermission.OPERATION_CHANGE_RECEIVERS, null, null);
        for (TaskReceiver receiver : receivers) {
            receiver.setId(null); // clear previous ID
            receiver.setActive(true);
            receiver.setCopy(copy);
            receiver.setTask(exst);
            if (receiver.getUser() != null) {
                receiver.setUser(new UserController().find(em, receiver.getUser().getUsername()));
            } else if (receiver.getGroup() != null) {
                receiver.setGroup(new UserController().findGroup(em, receiver.getGroup().getId()));
            }
            em.persist(receiver);
        }
        List<TaskReceiver> forRemove = copy ? exst.getCopies() : exst.getAssignees();
        for (TaskReceiver remove : forRemove) {
            em.remove(remove);
        }
        exst.getReceivers().removeAll(forRemove);
        exst.getReceivers().addAll(receivers);
        em.flush();
        indexTask(em, exst.getId());
        taskUpdated(em, user, exst, FlowPermission.OPERATION_CHANGE_RECEIVERS);
        new NotificationManager().notifyReceiverChange(em, user, exst, oldReceivers);
        return exst;
    }

    public Task changeSubject(EntityManager em, User user, Task task, String newSubject) {
        Task exst = findTask(em, task.getId());
        String oldSubject = exst.getSubject();
        validatePermission(em, exst, user, FlowPermission.OPERATION_CHANGE_SUBJECT, null, null);
        new TaskValidator().validateSubject(newSubject);
        exst.setSubject(newSubject);
        taskUpdated(em, user, exst, FlowPermission.OPERATION_CHANGE_SUBJECT);
        new NotificationManager().notifySubjectChange(em, user, exst, oldSubject);
        return exst;
    }

    public Task changePriority(EntityManager em, User user, Task task, Calendar dueDate, int priority) {
        Task exst = findTask(em, task.getId());
        int oldPriority = exst.getPriority();
        Calendar oldDueDate = exst.getDueDate();
        validatePermission(em, exst, user, FlowPermission.OPERATION_CHANGE_PRIORITY, null, null);
        new TaskValidator().validateDueDate(dueDate);
        exst.setDueDate(dueDate);
        exst.setPriority(priority);
        taskUpdated(em, user, exst, FlowPermission.OPERATION_CHANGE_PRIORITY);
        new NotificationManager().notifyPriorityChange(em, user, task, oldPriority, oldDueDate);
        return exst;
    }

    public Task changeStatus(EntityManager em, User user, Task task, String comment, int newStatus) {
        Task exst = findTask(em, task.getId());
        user = new UserController().find(em, user.getUsername());
        validatePermission(em, exst, user, FlowPermission.OPERATION_CHANGE_STATUS, newStatus, null);
        new TaskValidator().validateBody(comment);
        TaskBody body = new TaskBody();
        body.setActive(true);
        body.setBody(comment);
        body.setOwner(user);
        body.setTask(exst);
        body.setTaskStatus(newStatus);
        em.persist(body);
        exst.setTaskStatus(newStatus);
        exst.setLastBody(comment);
        exst.getBodies().add(body);
        taskUpdated(em, user, exst, FlowPermission.OPERATION_CHANGE_STATUS);
        new NotificationManager().notifyStatusChange(em, user, exst);
        return exst;
    }

    public TaskAttachment uploadAttachment(EntityManager em, User user, Task task, TaskAttachment binaryData, byte[] data) {
        // validate permission
        if (task != null) {
            task = findTask(em, task.getId());
            validatePermission(em, task, user, FlowPermission.OPERATION_ADD_ATTACHMENT, null, null);
        }

        // validate length
        int length = data.length;
        new FileValidator().validateLength(length);

        // get this user
        user = new UserController().find(em, user.getUsername());

        // create content
        long contentId = new BinaryDataController().createBinaryData(em, data);

        // create binary data
        binaryData.setBinaryId(contentId);
        binaryData.setOwner(user);
        binaryData.setActive(true);
        binaryData.setLength(length);
        em.persist(binaryData);

        // final operations
        if (task != null) {
            task.addAttachment(binaryData);
            binaryData.setTask(task);
            taskUpdated(em, user, task, FlowPermission.OPERATION_ADD_ATTACHMENT);
            new NotificationManager().notifyAttachmentManipulation(em, user, binaryData, false);
        }
        return binaryData;
    }

    // TODO: change downloadAttachment method!!!!!!

    public byte[] downloadAttachment(EntityManager em, User user, Task task, TaskAttachment binaryData) {
        binaryData = findAttachment(em, binaryData.getId());
        if (task != null) {
            task = findTask(em, task.getId());
            validatePermission(em, task, user, FlowPermission.OPERATION_DOWNLOAD_ATTACHMENT, null, binaryData);
        }
        return new BinaryDataController().getData(em, binaryData.getBinaryId());
    }

    public void removeAttachments(EntityManager em, User user, List<TaskAttachment> attachments) {
        // list of all affected tasks
        List<Task> affectedTasks = new ArrayList<Task>();

        // loop over attachments
        for (TaskAttachment att : attachments) {
            TaskAttachment attachment = findAttachment(em, att.getId());
            if (attachment.getTask() != null) {
                Task exst = findTask(em, attachment.getTask().getId());
                if (!affectedTasks.contains(exst)) {
                    affectedTasks.add(exst);
                }
                validatePermission(em, exst, user, FlowPermission.OPERATION_REMOVE_ATTACHMENT, null, attachment);
                exst.removeAttachment(attachment);
                new BinaryDataController().deleteBinaryData(em, attachment.getBinaryId());
                em.remove(attachment);
                em.flush(); // without this flushing multiple attachment remove gives bugs
            }
        }

        // validate update
        for (Task task : affectedTasks) {
            taskUpdated(em, user, task, FlowPermission.OPERATION_REMOVE_ATTACHMENT);
        }

        // send notifications
        for (TaskAttachment bin : attachments) {
            new NotificationManager().notifyAttachmentManipulation(em, user, bin, true);
        }
    }

    private void taskUpdated(EntityManager em, User user, Task task, int operation) {
        String sql = "SELECT index FROM TaskReceiverIndex index WHERE index.task.id = :task";
        List<TaskReceiverIndex> indecies = em.createQuery(sql)
                .setParameter("task", task.getId())
                .getResultList();
        for (TaskReceiverIndex index : indecies) {
            if (!index.getUser().equals(user)) {
                index.setNew(true);
            } else {
                index.setNew(false);
            }
        }
    }

    // </editor-fold>

    public byte[] getTaskReport(EntityManager em, long taskId) {
        try {
            Report report = new TaskReport(taskId);
            return report.getBytes(em);
        } catch(Exception ex) {
            ex.printStackTrace();
            throw new TaskException("ვერ ვაგენერირებ: " + ex.toString());
        }
    }

}
